/*==============================================================================
  File Name   : main.cpp

  Description : Entry point for TCP client developed by using MinGW.

  Created on  : 2009-02-04
  Created by  : Shiou Ming, Lee

  Updated on  : 2009-02-09
  Updated by  : Shiou Ming, Lee
  Changes     : version 0.1.1.2
                - Added some simple functionalities of a TCP client:
                  connect to TCP server, send data, receive data.
                - Added support for user to specify remote server info via CLI:
                  TCPClient [<connecting IP> <connecting port> <data>]
                version 0.1.0.1
                - Branched from "basic_app" template and redefine as TCP client.

  Cautions    : (important notes)
  ============================================================================*/


//------------------------------------------------------------------------------
//-- INCLUDES & DEFINES
//------------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#define _APP_VERSION_  _T("0.1.1.2")
#define _APP_NAME_     _T("TCP Client Demo")

#define _SCK_VERSION1_ 0x0101
#define _SCK_VERSION2_ 0x0202


//------------------------------------------------------------------------------
//-- TYPEDEF / STRUCT / UNION / ENUM
//------------------------------------------------------------------------------
struct ST_CMDLINE_OPTIONS
{
  bool bOptState_Help;
  bool bOptState_Version;
  bool bOptState_DebugMode;
};


//------------------------------------------------------------------------------
//-- GLOBAL
//------------------------------------------------------------------------------
ST_CMDLINE_OPTIONS g_stCmdLineOptions = {false, false, false};


//------------------------------------------------------------------------------
//-- FUNCTION PROTOTYPES
//------------------------------------------------------------------------------
bool HandleCmdLineOpt(int _argc, _TCHAR *_argv[]);
bool MatchCmdLineOpt(int _nKeywordCount, 
                     const _TCHAR *_szKeywordList[],
                     const _TCHAR *_szCmdLineArg);

bool SendToServer(const _TCHAR *_szIPAddress,
                  unsigned short _nPortNum, 
                  const _TCHAR *_szData);
void PrintLastSockError(const _TCHAR *_szAppErrMsg);


//------------------------------------------------------------------------------
//-- MAIN FUNCTION
//------------------------------------------------------------------------------
int _tmain(int _argc, _TCHAR *_argv[], _TCHAR *_envp[])
{
  //-- Try to process parameters in separate function, don't mess up main()
  if (!HandleCmdLineOpt(_argc, _argv)) return 0;
      
  //-- Main logic
  if (_argc == 4)
  {
    // Assumed that 3 parameter inputs will be: server IP, server port, data
    SendToServer(_argv[1], _ttoi(_argv[2]), _argv[3]);
  }
  else
  {
    SendToServer(_T("127.0.0.1"), 9999, _T("sample DATA from client"));
  }

  //-- Stop the console screen before quit for displaying the output.
  _tprintf(_T("\n\nPress <ENTER> key to exit"));
  int quitFlag = _fgettchar();

  return 0;
}


//------------------------------------------------------------------------------
//-- FUNCTION IMPLEMENTATIONS
//------------------------------------------------------------------------------
bool HandleCmdLineOpt(int _argc, _TCHAR *_argv[])
{
  bool bContinueMainLogic = true;
  
  // Declare the list of valid keywords for various command line options
  const int KWORDCOUNT_VERSION = 3;
  const _TCHAR *ARG_KEYWORD_VERSION[KWORDCOUNT_VERSION]
      = {_T("--version"), _T("-v"), _T("-ver")};

  const int KWORDCOUNT_HELP = 3;      
  const _TCHAR *ARG_KEYWORD_HELP[KWORDCOUNT_HELP]
      = {_T("--help"), _T("-h"), _T("?")};

  const int KWORDCOUNT_DEBUG = 2;      
  const _TCHAR *ARG_KEYWORD_DEBUG[KWORDCOUNT_DEBUG]
      = {_T("--debug"), _T("-d")};

  // Loop through command line options and take necessary actions
  //_tprintf(_T("\n=== Command Line Parameters ===\n"));
  for (int i = 0; i < _argc; i++)
  {
    if ((!g_stCmdLineOptions.bOptState_Version) && 
        (MatchCmdLineOpt(KWORDCOUNT_VERSION, ARG_KEYWORD_VERSION, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_Version = true;
      _tprintf(_T("\nVERSION INFO\n------------"));
      _tprintf(_T("\n%s version %s\n"), _APP_NAME_, _APP_VERSION_);
      bContinueMainLogic = false;
      break;
    }
    else if ((!g_stCmdLineOptions.bOptState_Help) && 
             (MatchCmdLineOpt(KWORDCOUNT_HELP, ARG_KEYWORD_HELP, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_Help = true;
      _tprintf(_T("\nHELP INFO\n---------"));
      _tprintf(_T("\nUSAGE: TCPClient [<server IP> <server port> <data>]\n"));
      bContinueMainLogic = false;
      break;
    }
    else if ((!g_stCmdLineOptions.bOptState_DebugMode) && 
             (MatchCmdLineOpt(KWORDCOUNT_DEBUG, ARG_KEYWORD_DEBUG, _argv[i])))
    {
      g_stCmdLineOptions.bOptState_DebugMode = true;
      _tprintf(_T("\nDEBUG MODE\n----------"));
      _tprintf(_T("\nRunning in debug mode!\n"));
    }
    else
    {
      //_tprintf(_T("\nParameter[%d]\t- %s\n"), i, _argv[i]);
    }
  }
  //_tprintf(_T("\n=== END OF PARAMETERS ===\n\n"));

  return bContinueMainLogic;
}


bool MatchCmdLineOpt(int _nKeywordCount, 
                     const _TCHAR *_szKeywordList[],
                     const _TCHAR *_szCmdLineArg)
{
  // Take one command line option and compare with the list of valid keywords,
  // return true if found matching.
  for (int i = 0; i < _nKeywordCount; i++)
  {
    if (_tcscmp(_szKeywordList[i], _szCmdLineArg) == 0) return true;
  }

  return false;
}


// _szIPAddress: IP address of TCP server for data to be send to.
// _nPortNum   : Connecting to this port at TCP server.
// _szData     : String data to be send to TCP server.
bool SendToServer(const _TCHAR *_szIPAddress, unsigned short _nPortNum, 
                  const _TCHAR *_szData)
{
  SOCKET clientSocket;
  WSADATA wsaInfo;

  // Step 1: Init WinSock (for WinSock only, not necessary for Unix/Linux)
  int nError = WSAStartup(_SCK_VERSION2_, &wsaInfo);
  if (nError)
  {
    _tprintf(_T("\nError: WSAStartup() failed [%d]"), nError);
    return false;  // Check if WinSock init successful
  }

  // Verify if WinSock version matches with desired one (WinSock only)
  if (wsaInfo.wVersion != _SCK_VERSION2_)
  {
    _tprintf(_T("\nError: WinSock version unmatch."));
    _tprintf(_T("\nAction: Clean up then exit.\n"));
    WSACleanup();
    return false;
  }

  // Step 2: Create the socket with socket() function
  //   Param 2: SOCK_STREAM = TCP; SOCK_DGRAM = UDP; ...
  //   Param 3: IPPROTO_IP (0); IPPROTO_TCP (6); IPPROTO_UDP(17); ...
  // Normally for TCP and UDP, param 3 (protocol) can be 0 (IPPROTO_IP) as well
  clientSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (INVALID_SOCKET == clientSocket)
  {
    PrintLastSockError(_T("\nError socket()"));
    _tprintf(_T("\nAction: Clean up then exit.\n"));
    WSACleanup();
    return false;
  }

  sockaddr_in destAddress;
  memset(&destAddress, 0, sizeof(destAddress));
  destAddress.sin_family = AF_INET;
  destAddress.sin_port = htons(_nPortNum);
  destAddress.sin_addr.s_addr = inet_addr(_szIPAddress);

  // Step 2: Trigger connection to remote end with connect() function
  _tprintf(_T("\nConnecting to ... \t[%s:%d]\n"), _szIPAddress, _nPortNum);
  if (SOCKET_ERROR == 
        connect(clientSocket, (sockaddr *) &destAddress, sizeof(destAddress)))
  {
    PrintLastSockError(_T("\nError connect()"));
    _tprintf(_T("\nAction: Clean up then exit.\n"));
    WSACleanup();
    return false;
  }
  _tprintf(_T("Connected.\n"));

  // Step 3: Typically, client will send data to the connected destination.
  UINT sentBufferLen;
  _tprintf(_T("Sending data ... \t[%s]\n"), _szData);
  sentBufferLen = send(clientSocket, _szData, _tcslen(_szData), 0);
  if (SOCKET_ERROR == sentBufferLen)
    PrintLastSockError(_T("\nError socket send()"));
  else
    _tprintf(_T("Data sent.\n"));

  // Step 4: Typically connected server will return something (result) to client
  UINT recvBufferLen;
  const UINT BUFFER_SIZE = 32;
  _TCHAR szBuffer[BUFFER_SIZE] = {0};

  _tprintf(_T("Receiving data ...\n"));
  recvBufferLen = recv(clientSocket, szBuffer, BUFFER_SIZE - 1, 0);
  if (SOCKET_ERROR == recvBufferLen)
    PrintLastSockError(_T("\nError socket recv()"));
  else
    _tprintf(_T("Received data \t\t[%s]\n"), szBuffer);

  // Remember to close the socket and do cleanup
  closesocket(clientSocket);
  WSACleanup();
  
  return true;
}


void PrintLastSockError(const _TCHAR *_szAppErrMsg)
{
#ifdef _WIN32
  int nLastSockErrCode = WSAGetLastError();
  _tprintf(_T("\n%s: [%d] %s\n"),
           _szAppErrMsg, nLastSockErrCode, gai_strerror(nLastSockErrCode));
#else
  _tperror(_szAppErrMsg);
#endif
}
